import throttle from 'lodash.throttle';
import type Editor from '../../editor';

import Operation from "../../operations/operation"
import { InsertTextOperation, SetSelectionOperation, RemoveTextOperation } from '../../operations'

interface CombinedOperation {
    op: Operation  //  原子层操作
    undoSelection: SetSelectionOperation  //撤销时光标所在位置
    redoSelection: SetSelectionOperation  //撤销时光标所在位置
}
/**
 * 最大可回撤步数
 * 
 * 这个参数用于控制 this.undos 的最大长度是多少
 * 如果 this.redos 不为空的时候，又往 this.undos 中加入（插入，撤销，重做引发的）了 operation
 * 那么 this.redos 需要置空
 */

interface UndoRedoPluginConfig {
    maxUndoCount: number
}
/**
 * 撤销重做插件
 */
export class UndoRedoPlugin {

    private ops: CombinedOperation[] = []   // 一定事件内的 operation 用于聚合多个撤销动作
    private undos: typeof this.ops[] = []   // 撤销二维数组
    private redos: typeof this.ops[] = []   // 撤销二维数组
    private config: UndoRedoPluginConfig = { maxUndoCount: 50 }     //插件的配置对象

    constructor(config?: UndoRedoPluginConfig) {
        this.config = Object.assign(this.config, config)

        debugger
        // 将 300ms 之内的原子操作聚合到一起
        this.polymerizeOperation = throttle(this.polymerizeOperation.bind(this), 300, {
            leading: false,
            trailing: true,
        });
    }

    run(editor: Editor) {
        const apply = editor.apply.bind(editor);

        // 重写 editor 的 apply
        editor.apply = (op) => {
            // 如果 redos 数组中存在 operation, 此时又执行了插入/删除, 则需要重置 redos
            this.restRedos();
            this.pushOperationIntoOps(op, editor);
            this.polymerizeOperation();
            apply(op);
        };

        // 重写 editor 中的 undo
        editor.undo = this.undo.bind(this, editor);
        // 重写 editor 中的 redo
        editor.redo = this.redo.bind(this, editor);
    }

    /** 将 operation 推入 ops 中, 用于聚合撤销动作 */
    pushOperationIntoOps(op: Operation, editor: Editor) {
        // 重做时光标所在的位置
        let anchor = 0,
            focus = anchor;

        // 如果是插入操作, 那么重做时也是插入
        // 所以此时光标的位置应该在现有光标的开始位置上加上插入字符的长度
        if (op instanceof InsertTextOperation) {
            anchor = focus = op.getIndex() + op.getSpacers().length;
        }

        // 如果是删除操作, 那么重做时也是删除
        // 所以此时光标的位置应该在现有光标的结束位置上再减去删除字符的长度
        if (op instanceof RemoveTextOperation) {
            // 1. 先获取现有光标的开始位置和结束位置
            const { startIndex, endIndex } = op.getIndex();
            // 2. 由于不能保证 startIndex 一定大于 endIndex, 所以获取它俩的最大值
            const maxIndex = Math.max(startIndex, endIndex);
            // 3. 由于此时拿不到删除后的字符, 所以 startIndex - endIndex 的绝对值就是删除字符的长度
            const removeStrLen = Math.abs(startIndex - endIndex);
            // 4. 此时重做(删除)后的光标位置就是光标的结束位置 - 删除字符的长度
            anchor = focus = maxIndex - removeStrLen;
        }

        this.ops.push({
            op,
            undoSelection: new SetSelectionOperation(editor.getSelectionModel().getSelection()),
            redoSelection: new SetSelectionOperation({ anchor, focus }),
        });
    }

    undo(editor: Editor) {
        console.log(JSON.parse(JSON.stringify(this.undos)), 'undos');

        if (this.undos.length) {
            const undo = this.undos.at(-1);

            if (undo) {
                // 由于 undos 是 push 进去的, 所以撤销时从后往前依次执行
                for (let i = undo.length - 1; i >= 0; i--) {
                    const { op, undoSelection } = undo[i];
                    op.inverse(editor);
                    undoSelection.apply(editor);
                }

                // 撤销完之后删除这一组 operation
                this.undos.pop();
                // 将这组 operation 存入重做数组中
                this.redos.push(undo);
            }
        }
    }
    redo(editor: Editor) {
        console.log(JSON.parse(JSON.stringify(this.redos)), 'redos');

        if (this.redos.length) {
            const redo = this.redos.at(-1);

            if (redo) {
                // 重做时从前往后依次遍历即可
                redo.forEach(({ op, redoSelection }) => {
                    op.apply(editor);
                    redoSelection.apply(editor);
                });

                // 重做完之后删除这一组 operation
                this.redos.pop();
                // 将这组 operation 存入撤销数组中
                this.undos.push(redo);
            }
        }
    }
    /** 聚合原子操作 */
    polymerizeOperation() {
        const { maxUndoCount } = this.config;

        if (this.undos.length >= maxUndoCount) {
            this.undos.shift();
        }

        this.undos.push(this.ops);
        this.ops = [];
    }
    /** 重置 redos */
    restRedos() {
        if (this.redos.length) {
            this.redos = [];
        }
    }
}

export default UndoRedoPlugin

